use std::collections::VecDeque;

use crate::destor;

extern "C" {
    pub fn fastcdc_chunk_data(p:*const libc::c_uchar,n:libc::c_int) -> libc::c_int;
    pub fn fastcdc_sample_chunk_data(p:*const libc::c_uchar,n:libc::c_int) -> libc::c_int;
    pub fn rabin_chunk_data(p:*const libc::c_uchar,n:libc::c_int) -> libc::c_int;
    pub fn rabin_window_avx8_init(w:*mut RabinWindowAvx8,p:*const libc::c_uchar, n:libc::c_uint);
    pub fn rabin_chunk_data_avx8(w:*mut RabinWindowAvx8) -> libc::c_uchar;
    pub fn rabin_window_cursor(w:*const RabinWindowAvx8) -> libc::c_uint;
    pub fn know_rabin_window_size();
}

pub struct SplitCollector{
    splits:Vec<VecDeque<usize>>,
    region_num:usize,
    region_size:usize,
    cursor:usize,
    last:usize,
    end:bool,
}

impl SplitCollector {
    pub fn new(region_num:usize,region_size:usize) -> Self {
        Self {
            splits:vec![VecDeque::new();region_num],
            region_num:region_num,
            region_size:region_size,
            cursor:0,
            last:0,
            end:true,
        }
    }

    pub fn refresh(&mut self) {
        assert!(self.end);
        self.end = false;
        self.cursor = 0;
        self.last = 0;
    }

    pub fn push(&mut self,result:u8,cursor:usize) {
        for i in 0..self.region_num {
            if result & (1<<i) != 0 {
                self.splits[i].push_back(i*self.region_size + cursor);
            }
        }
    }


    pub fn pop(&mut self) -> Option<usize> {
        let chunk_max_size = unsafe { destor.chunk_max_size() };
        let chunk_min_size = unsafe { destor.chunk_min_size() };
        for i in self.cursor..self.region_num {
            if let Some(point) = self.splits[i].pop_front() {
                let last = self.last;
                if last >= point || point - last < chunk_min_size { continue; } 
                let cut_len = point - last;
                self.last = point;
                self.cursor = i;
                return Some(cut_len);
            }
        }
        self.cursor = self.region_num;
        self.end = true;
        if self.last == chunk_max_size {
            self.last = chunk_max_size;
            return Some(chunk_max_size);
        }
        return None;
    }
}

const RABIN_WINDOW_AVX8_SIZE:usize = 512;

#[repr(C,align(64))]
pub struct RabinWindowAvx8 {
	inner:[u8;RABIN_WINDOW_AVX8_SIZE],
}

impl RabinWindowAvx8 {
    pub unsafe fn new() -> Self {
        Self {
            inner:[0;RABIN_WINDOW_AVX8_SIZE],
        }
    }

    pub unsafe fn refresh(&mut self,data:&[u8]) {
        rabin_window_avx8_init(self as *mut RabinWindowAvx8,(&data[0]) as *const libc::c_uchar, data.len() as libc::c_uint);
    }

    pub unsafe fn split(&mut self) -> u8 {
        rabin_chunk_data_avx8(self as *mut RabinWindowAvx8)
    }

    pub unsafe fn cursor(&self) -> usize {
        rabin_window_cursor(self).try_into().unwrap()
    }
}

pub fn rabin_chunking(data:&[u8]) -> usize {
    unsafe { rabin_chunk_data(&data[0] as *const libc::c_uchar,data.len().try_into().unwrap()).try_into().unwrap() }
}

pub fn fastcdc_chunking(data:&[u8]) -> usize {
    unsafe { fastcdc_chunk_data(&data[0] as *const libc::c_uchar,data.len().try_into().unwrap()).try_into().unwrap() }
}

pub fn fastcdc_sample_chunking(data:&[u8]) -> usize {
    unsafe { fastcdc_sample_chunk_data(&data[0] as *const libc::c_uchar,data.len().try_into().unwrap()).try_into().unwrap() }
}